package com.boarsoft.boar.app.action;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.DataFormatException;

import javax.servlet.http.HttpServletRequest;

import com.boarsoft.boar.context.ActionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.boarsoft.agent.AgentService;
import com.boarsoft.bean.ReplyInfo;
import com.boarsoft.boar.BaseConfig;
import com.boarsoft.boar.app.AppBiz;
import com.boarsoft.boar.app.AppInstBiz;
import com.boarsoft.boar.common.Constants;
import com.boarsoft.boar.entity.AppInfo;
import com.boarsoft.boar.entity.AppInst;
import com.boarsoft.boar.entity.ServerInfo;
import com.boarsoft.boar.server.ServerBiz;
import com.boarsoft.common.Authorized;
import com.boarsoft.common.Util;
import com.boarsoft.common.dao.PagedResult;
import com.boarsoft.common.util.BeanUtil;
import com.boarsoft.common.util.HttpClientUtil;
import com.boarsoft.common.util.JsonUtil;
import com.boarsoft.rpc.core.RpcContext;
import com.boarsoft.soagov.bean.JvmStatus;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@RestController
@RequestMapping("/app/inst")
public class AppInstAction {
	private static final Logger log = LoggerFactory.getLogger(AppInstAction.class);

	@Autowired
	private AppInstBiz appInstBiz;
	@Autowired
	private ServerBiz serverBiz;
	@Autowired
	private AppBiz appBiz;
	@Autowired
	@Lazy(value = true)
	@Qualifier("agentX")
	private AgentService agentX;
	@Autowired
	@Qualifier("svcControllers")
	@Lazy(true)
	private Map<String, Object> svcControllers;

	@RequestMapping("/regist.do")
	public ReplyInfo<Object> regist(String appId, String env, HttpServletRequest req) {
		String ip = ActionContext.getRemoteIp(req);
		if (Util.strIsEmpty(ip)) {
			return new ReplyInfo<>("Can not get your IP");
		}
		String rpcport = req.getParameter("rpcport");
		String cluster = req.getParameter("cluster");
		String zone = req.getParameter("zone");
		log.info("appId={}, evn={}, cluster={}, zone={}", appId, env, cluster, zone);
		AppInfo appInfo = appBiz.get(appId);
		AppInst ai = new AppInst();
		if (null != appInfo) {
			ai = appInstBiz.find(ip,Integer.parseInt(rpcport));
			if(ai==null){
				ai=new AppInst();
				ai.setIp(ip);
				ai.setPort(Integer.parseInt(rpcport));
				ai.setAppId(appId);
				ai.setStatus(AppInst.STATUS_RUNNING);
				ai.setDeployPath(appInfo.getDeployPath());
				ai.setServerId("00000000000000000000000000000000");
				ai.setUser(appInfo.getUser());
				ai.setTempPath(appInfo.getTempPath());
				ai.setCluster(cluster);
				ai.setEnv(env);
				ai.setZone(zone);
				appInstBiz.add(ai);
			}else{
				ai.setAppId(appId);
				ai.setStatus(AppInst.STATUS_RUNNING);
				ai.setDeployPath(appInfo.getDeployPath());
				ai.setServerId("00000000000000000000000000000000");
				ai.setUser(appInfo.getUser());
				ai.setTempPath(appInfo.getTempPath());
				ai.setCluster(cluster);
				ai.setEnv(env);
				ai.setZone(zone);
				appInstBiz.update(ai);
			}
		}

		return new ReplyInfo<Object>(true, ai.getZone());
	}

	@JsonIgnoreProperties({ "apps", "files", "insts", "libs", "svcs", "protocols" })
	@RequestMapping("/list.do")
	@Authorized(code = "app.inst.list")
	public ReplyInfo<Object> list(String addr, String env, String serverId, String projId, String group, Short status,
			String appId, String key, String orderBy, int pageNo, int pageSize) {
		PagedResult<AppInst> pr = appInstBiz.list(//
				env, serverId, appId, projId, group, status, key, orderBy, pageNo, pageSize);
		// 遍历所有的服务实例，检查它们的状态？
		// List<AppInst> lt = pr.getList();
		// AppInfo app = appBiz.get(appId);
		// String h = app.getHandler();
		// if (Util.strIsEmpty(h)) {
		// h = AppInfo.HANDLER_DEFAULT;
		// }
		// AppHandler handler = null;
		// try {
		// handler = this.applicationContext.getBean(h, AppHandler.class);
		// } catch (Exception e) {
		// log.error("Error on get handler {} of application {}", handler, app,
		// e);
		// }
		// if (handler != null) {
		// for (AppInst o : lt) {
		// if (o.getStatus() == AppInst.STATUS_DISABLED) {
		// continue;
		// }
		// try {
		// o.setStatus(handler.test(o.getAddr()));
		// appInstBiz.update(o);
		// } catch (Exception e) {
		// log.error("Error on test app instance {}", addr, e);
		// o.setStatus(AppInst.STATUS_FAILED);
		// }
		// }
		// }
		return new ReplyInfo<Object>(true, pr);
	}

	@RequestMapping("/save.do")
	@Authorized(code = "app.inst.save")
	public ReplyInfo<Object> save(AppInst o) {
		if (Util.strIsEmpty(o.getId())) {
			return this.add(o);
		}
		return this.update(o);
	}

	@RequestMapping("/add.do")
	@Authorized(code = "app.inst.add")
	public ReplyInfo<Object> add(AppInst o) {
		AppInfo a = appBiz.get(o.getAppId());
		ServerInfo s = serverBiz.get(o.getServerId());
		String ip = s.getIp();
		int port = o.getPort();
		port = port > 0 ? port : a.getPort();
		AppInst x = appInstBiz.find(ip, port);
		if (x != null) {
			return Constants.REPLY_INFO_DUPLICATED;
		}
		o.setIp(ip);
		o.setPort(port);
		o.setEnv(s.getEnv());
		appInstBiz.add(o);
		return ReplyInfo.SUCCESS;
	}

	@RequestMapping("/update.do")
	@Authorized(code = "app.inst.update")
	public ReplyInfo<Object> update(AppInst p) {
		// 仅允许修改以下字段
		AppInst o = appInstBiz.get(p.getId());
		o.setTempPath(p.getTempPath());
		o.setNo(p.getNo());
		o.setDeployPath(p.getDeployPath());
		o.setTempPath(p.getTempPath());
		o.setUser(p.getUser());
		appInstBiz.update(o);
		return ReplyInfo.SUCCESS;
	}

	/**
	 * 使用默认值批量添加应用与服务器的部署关系
	 * 
	 * @return
	 */
	@RequestMapping("/bind.do")
	@Authorized(code = "app.inst.bind")
	public ReplyInfo<Object> bind(String appIds, String serverIds) {
		appInstBiz.bind(appIds, serverIds.split(","));
		return ReplyInfo.SUCCESS;
	}

	/**
	 * 删除一条appId与serverId的关联关系
	 * 
	 * @return
	 */
	@RequestMapping("/delete.do")
	@Authorized(code = "app.inst.delete")
	public ReplyInfo<Object> delete(String id) {
		String[] a = id.split(",");
		for (String k : a) {
			appInstBiz.delete(k);
		}
		return ReplyInfo.SUCCESS;
	}

	// /**
	// * 读取对应的JENKINS的XML配置
	// *
	// * @param projCode-工程代码
	// * @param a-应用信息
	// * @return String
	// * @throws IOException
	// */
	// private String readJenkinsConfig(String projCode, AppInfo a) throws
	// IOException {
	// StringBuilder sb = new
	// StringBuilder().append(DeployServerConfig.PATH_PRIVATE).append("/jenkins/").append(projCode).append("/")
	// .append(a.getCode()).append("/");
	// String fn;
	// if (Util.strIsNotEmpty(a.getVersion())) {
	// fn = new
	// StringBuilder().append(sb).append(a.getVersion()).append(".xml").toString();
	// File f = new File(fn);
	// if (f.exists()) {
	// return FileUtil.read(f);
	// }
	// }
	// fn = sb.append("default.xml").toString();
	// return FileUtil.read(new File(fn));
	// }

	// @RequestMapping("/undeploy")
	// @Authorized(code = "app.inst.undeploy")
	@Deprecated
	public ReplyInfo<Object> undeploy2() {
		// AppInst o = appInstBiz.get(id);
		// 调起JENKINS的移除部署JOB将其移除，移除时会先停止
		// Map<String, String> pm = new HashMap<String, String>();
		// pm.put("path", o.getPath());
		try {
			// 调起卸载应用的SHELL，该SHELL成功执行完成后回调
			// JenkinsBroker.build("undeploy", pm);
			return ReplyInfo.SUCCESS;
		} catch (Exception e) {
			log.error("Error on undeploy this instance", e);
			return Constants.REPLY_ERROR_INTERNAL;
		}
	}

	@RequestMapping("/get.do")
	@Authorized(code = "app.inst.get")
	public ReplyInfo<String> get(String id) {
		AppInst o = appInstBiz.get(id);
		return new ReplyInfo<String>(true, JsonUtil.from(o));
	}

	@RequestMapping("/event.do")
	@Authorized(code = "app.inst.event")
	public ReplyInfo<Object> event(HttpServletRequest request) {
		// 读取请求内容
		try {
			// appInstBiz.status(id, AppInst.STATUS_RUNNING);
			// appInstBiz.status(id, AppInst.STATUS_DEPLOYING);
			// appInstBiz.status(id, AppInst.STATUS_FINALIZED);
			// appInstBiz.delete(id); // 成功再删除实例记录
			String rs = HttpClientUtil.readStr(request.getInputStream(), "UTF-8");
			System.out.println(rs);
		} catch (Exception e) {
			log.error("Error on handle jenkins notification", e);
		}
		return ReplyInfo.SUCCESS;
	}

	/**
	 * 部署但不启动
	 * 
	 * @return
	 */
	@RequestMapping("/enable.do")
	@Authorized(code = "app.inst.enable")
	public ReplyInfo<Object> enable(String id) {
		// TODO 检查实例状态（是否在运行中）?
		appInstBiz.status(id, AppInst.STATUS_STOPPED);
		return ReplyInfo.SUCCESS;
	}

	@RequestMapping("/disable.do")
	@Authorized(code = "app.inst.disable")
	public ReplyInfo<Object> disable(String id) {
		// 停用应用：将状态修改为“停用”
		appInstBiz.status(id, AppInst.STATUS_DISABLED);
		return ReplyInfo.SUCCESS;
	}

	/*
	 * 根据IP和端口查询
	 */
	@RequestMapping("/find.do")
	public ReplyInfo<String> find(String ip, int port) {
		AppInst o = appInstBiz.find(ip, port);
		return new ReplyInfo<String>(true, JsonUtil.from(o, "app,server"));
	}

	@RequestMapping("/start.do")
	public ReplyInfo<Object> start(String id) {
		// 更新状态
		appInstBiz.status(id, AppInst.STATUS_STARTING);
		AppInst inst = appInstBiz.get(id);
		AppInfo app = appBiz.get(inst.getAppId());
		String addr = new StringBuilder().append(inst.getIp()).append(":")//
				.append(BaseConfig.AGENTX_PORT).toString();
		String dp = inst.getDeployPath();
		String sc = app.getStartCode();
		log.info("Ask {} startup app instance {} with: {}", addr, inst, sc);
		// 取inst的path而不是app的deployPath
		RpcContext.specify2(addr);
		try {
			// 执行应用实例目录下的启动脚本
			agentX.executeShell(dp, sc, new String[] { //
					dp, inst.getIp(), String.valueOf(inst.getPort()) });
			appInstBiz.status(id, AppInst.STATUS_RUNNING);
			log.info("App instance {} is started successfully", inst);
			return ReplyInfo.SUCCESS;
		} catch (Exception e) {
			log.error("Error on start app instance {}", inst, e);
			return Constants.REPLY_ERROR_INTERNAL;
		} finally {
			RpcContext.specify2(null);
		}
	}

	@RequestMapping("/stop.do")
	public ReplyInfo<Object> stop(String id) {
		// 更新状态
		appInstBiz.status(id, AppInst.STATUS_STOPPING);
		AppInst inst = appInstBiz.get(id);
		AppInfo app = appBiz.get(inst.getAppId());
		String addr = new StringBuilder().append(inst.getIp()).append(":")//
				.append(BaseConfig.AGENTX_PORT).toString();
		String dp = inst.getDeployPath();
		String sc = app.getShutdownCode();
		log.info("Ask {} stop app instance {} with: {}", addr, inst, sc);
		// 取inst的path而不是app的deployPath
		RpcContext.specify2(addr);
		try {
			// 执行应用实例目录下的启动脚本
			agentX.executeShell(dp, sc, new String[] { //
					dp, inst.getIp(), String.valueOf(inst.getPort()) });
			appInstBiz.status(id, AppInst.STATUS_STOPPED);
			log.info("App instance {} is stopped successfully", inst);
			return ReplyInfo.SUCCESS;
		} catch (Exception e) {
			log.error("Error on stop app instance {}", inst, e);
			return Constants.REPLY_ERROR_INTERNAL;
		} finally {
			RpcContext.specify2(null);
		}
	}

	private void getJmvInfo(JvmStatus jvmStat, String[] par, String shellName) throws DataFormatException {
		ReplyInfo<Object> ro;
		try {
			ro = agentX.executeCmd(shellName, par, null);
			if (!ro.isSuccess()) {
				log.error("Get JVM info failed: {}", ro.getData());
				throw new RuntimeException(String.valueOf(ro.getData()));
			}
		} catch (IOException | InterruptedException e) {
			log.error("Error on call remote shell {}", shellName, e);
			return;
		}
		String jvmInfo = String.valueOf(ro.getData());
		String[] arrInfo = jvmInfo.split(",");
		Map<String, Object> ret = new HashMap<String, Object>();
		for (int i = 0; i < arrInfo.length; i++) {
			String[] info = arrInfo[i].trim().split("=");
			if (info.length < 2 || Util.strIsEmpty(info[0])) {
				log.error("数据[{}]错误, data=[{}]", i, jvmInfo);
				throw new DataFormatException(String.format("数据[%d]错误, data=[%s]", i, jvmInfo));
			}
			ret.put(info[0].trim(), Float.valueOf(info[1]));
		}
		BeanUtil.fillObjectWithMap(jvmStat, ret);
		return;
	}

	/**
	 * 功能: 获取远程java应用jvm信息 ip: 应用服务器ip port: 端口(应用服务器agentX端口) key: 进程关键词
	 * 
	 * @return
	 */
	@RequestMapping("/stat.do")
	// @Authorized(code = "app.inst.stat")
	public ReplyInfo<Object> stat(String addr, String key) {
		if (Util.strIsEmpty(addr)) {
			return new ReplyInfo<Object>(Constants.WARN_INVALID, "addr");
		}
		if (Util.strIsEmpty(key)) {
			return new ReplyInfo<Object>(Constants.WARN_INVALID, "key");
		}
		String ip = addr.split(":")[0];
		RpcContext.specify2(String.format("%s:%d", ip, BaseConfig.AGENTX_PORT));
		try {
			JvmStatus jvmStat = new JvmStatus();
			String[] par = { "1", key.trim() };
			getJmvInfo(jvmStat, par, "jvminfo");
			getJmvInfo(jvmStat, par, "jvmclass");
			getJmvInfo(jvmStat, par, "jvmthread");
			return new ReplyInfo<Object>(true, jvmStat);
		} catch (Exception e) {
			log.error("Error on get stat of JVM", e);
			return new ReplyInfo<Object>(false, e.getMessage());
		} finally {
			RpcContext.specify2(null);
		}
	}

	@RequestMapping("/register.do")
	// @Authorized(code = "app.inst.stat")
	public ReplyInfo<Object> register(HttpServletRequest req, String appId, int port, String path) {
		String ip = req.getRemoteAddr();
		log.info("Remote app inst {}/{}:{} starting, path = {}", appId, ip, port, path);
		AppInfo a = appBiz.get(appId);
		if (a == null) {
			return Constants.REPLY_INFO_NOTFOUND;
		}
		AppInst o = appInstBiz.find(ip, port);
		if (o == null) {
			o = new AppInst();
		}
		o.setAppId(appId);
		o.setDeployPath(path);
		o.setIp(ip);
		o.setPort(port);
		ServerInfo s = serverBiz.find(a.getProjId(), ip);
		o.setEnv(s.getEnv());
		o.setServerId(s.getId());
		// o.setGroup(s.getGroup());
		o.setUser(a.getUser());
		o.setStatus(AppInst.STATUS_STARTING);
		// o.setLastTime()
		appInstBiz.save(o);
		return ReplyInfo.SUCCESS;
	}

	@RequestMapping("/deregister.do")
	// @Authorized(code = "app.inst.stat")
	public ReplyInfo<Object> deregister(HttpServletRequest req, String appId, int port, String path) {
		String ip = req.getRemoteAddr();
		log.info("Remote app inst {}/{}:{} stopped, path = {}", appId, ip, port, path);
		AppInst o = appInstBiz.find(ip, port);
		if (o == null) {
			return Constants.REPLY_INFO_NOTFOUND;
		}
		o.setStatus(AppInst.STATUS_STOPPED);
		// o.setLastTime()
		appInstBiz.save(o);
		return ReplyInfo.SUCCESS;
	}
}